// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraSettingsLocal.h"
#include "Engine/Engine.h"
#include "Framework/Application/SlateApplication.h"
#include "Engine/World.h"
#include "Misc/App.h"
#include "CommonInputSubsystem.h"
#include "GenericPlatform/GenericPlatformFramePacer.h"
#include "Player/LyraLocalPlayer.h"
#include "Performance/LatencyMarkerModule.h"
#include "Performance/LyraPerformanceStatTypes.h"
#include "ICommonUIModule.h"
#include "CommonUISettings.h"
#include "SoundControlBusMix.h"
#include "Widgets/Layout/SSafeZone.h"
#include "Performance/LyraPerformanceSettings.h"
#include "DeviceProfiles/DeviceProfileManager.h"
#include "DeviceProfiles/DeviceProfile.h"
#include "HAL/PlatformFramePacer.h"
#include "Development/LyraPlatformEmulationSettings.h"
#include "SoundControlBus.h"
#include "AudioModulationStatics.h"
#include "Audio/LyraAudioSettings.h"
#include "Audio/LyraAudioMixEffectsSubsystem.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraSettingsLocal)

// 定义游戏标签
UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_BinauralSettingControlledByOS, "Platform.Trait.BinauralSettingControlledByOS");

// 性能统计标签命名空间
namespace PerfStatTags
{
	UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_SupportsLatencyStats, "Platform.Trait.SupportsLatencyStats");
	UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Platform_Trait_SupportsLatencyMarkers, "Platform.Trait.SupportsLatencyMarkers");
}

//////////////////////////////////////////////////////////////////////

// 编辑器专用控制台变量
#if WITH_EDITOR
static TAutoConsoleVariable<bool> CVarApplyFrameRateSettingsInPIE(TEXT("Lyra.Settings.ApplyFrameRateSettingsInPIE"),
	false, // 默认值
	TEXT("Should we apply frame rate settings in PIE?"), // 描述：我们是否应在PIE中应用帧率设置？
	ECVF_Default);

static TAutoConsoleVariable<bool> CVarApplyFrontEndPerformanceOptionsInPIE(TEXT("Lyra.Settings.ApplyFrontEndPerformanceOptionsInPIE"),
	false, // 默认值
	TEXT("Do we apply front-end specific performance options in PIE?"), // 描述：我们是否在PIE中应用前端特定的性能选项？
	ECVF_Default);

static TAutoConsoleVariable<bool> CVarApplyDeviceProfilesInPIE(TEXT("Lyra.Settings.ApplyDeviceProfilesInPIE"),
	false, // 默认值
	TEXT("Should we apply experience/platform emulated device profiles in PIE?"), // 描述：我们是否应在PIE中应用经验/平台模拟的设备配置文件？
	ECVF_Default);
#endif

//////////////////////////////////////////////////////////////////////
// 控制台帧步调

static TAutoConsoleVariable<int32> CVarDeviceProfileDrivenTargetFps(
	TEXT("Lyra.DeviceProfile.Console.TargetFPS"),
	-1, // 默认值
	TEXT("Target FPS when being driven by device profile"), // 描述：由设备配置文件驱动时的目标FPS
	ECVF_Default | ECVF_Preview);

static TAutoConsoleVariable<int32> CVarDeviceProfileDrivenFrameSyncType(
	TEXT("Lyra.DeviceProfile.Console.FrameSyncType"),
	-1, // 默认值
	TEXT("Sync type when being driven by device profile. Corresponds to r.GTSyncType"), // 描述：由设备配置文件驱动时的同步类型。对应于r.GTSyncType
	ECVF_Default | ECVF_Preview);

//////////////////////////////////////////////////////////////////////
// 移动设备帧步调

static TAutoConsoleVariable<int32> CVarDeviceProfileDrivenMobileDefaultFrameRate(
	TEXT("Lyra.DeviceProfile.Mobile.DefaultFrameRate"),
	30, // 默认值
	TEXT("Default FPS when being driven by device profile"), // 描述：由设备配置文件驱动时的默认FPS
	ECVF_Default | ECVF_Preview);

static TAutoConsoleVariable<int32> CVarDeviceProfileDrivenMobileMaxFrameRate(
	TEXT("Lyra.DeviceProfile.Mobile.MaxFrameRate"),
	30, // 默认值
	TEXT("Max FPS when being driven by device profile"), // 描述：由设备配置文件驱动时的最大FPS
	ECVF_Default | ECVF_Preview);

//////////////////////////////////////////////////////////////////////

static TAutoConsoleVariable<FString> CVarMobileQualityLimits(
	TEXT("Lyra.DeviceProfile.Mobile.OverallQualityLimits"),
	TEXT(""), // 默认值
	TEXT("List of limits on resolution quality of the form \"FPS:MaxQuality,FPS2:MaxQuality2,...\", kicking in when FPS is at or above the threshold"), // 描述：分辨率质量限制列表，形式为"FPS:MaxQuality,FPS2:MaxQuality2,..."，当FPS达到或超过阈值时生效
	ECVF_Default | ECVF_Preview);

static TAutoConsoleVariable<FString> CVarMobileResolutionQualityLimits(
	TEXT("Lyra.DeviceProfile.Mobile.ResolutionQualityLimits"),
	TEXT(""), // 默认值
	TEXT("List of limits on resolution quality of the form \"FPS:MaxResQuality,FPS2:MaxResQuality2,...\", kicking in when FPS is at or above the threshold"), // 描述：分辨率质量限制列表，形式为"FPS:MaxResQuality,FPS2:MaxResQuality2,..."，当FPS达到或超过阈值时生效
	ECVF_Default | ECVF_Preview);

static TAutoConsoleVariable<FString> CVarMobileResolutionQualityRecommendation(
	TEXT("Lyra.DeviceProfile.Mobile.ResolutionQualityRecommendation"),
	TEXT("0:75"), // 默认值
	TEXT("List of limits on resolution quality of the form \"FPS:Recommendation,FPS2:Recommendation2,...\", kicking in when FPS is at or above the threshold"), // 描述：分辨率质量限制列表，形式为"FPS:Recommendation,FPS2:Recommendation2,..."，当FPS达到或超过阈值时生效
	ECVF_Default | ECVF_Preview);

//////////////////////////////////////////////////////////////////////

/**
 * FLyraScalabilitySnapshot构造函数
 */
FLyraScalabilitySnapshot::FLyraScalabilitySnapshot()
{
	static_assert(sizeof(Scalability::FQualityLevels) == 88, "This function may need to be updated to account for new members"); // 静态断言：如果Scalability::FQualityLevels大小改变，此函数可能需要更新

	// 初始化质量级别为无效值
	Qualities.ResolutionQuality = -1.0f; // 分辨率质量
	Qualities.ViewDistanceQuality = -1; // 视距质量
	Qualities.AntiAliasingQuality = -1; // 抗锯齿质量
	Qualities.ShadowQuality = -1; // 阴影质量
	Qualities.GlobalIlluminationQuality = -1; // 全局光照质量
	Qualities.ReflectionQuality = -1; // 反射质量
	Qualities.PostProcessQuality = -1; // 后期处理质量
	Qualities.TextureQuality = -1; // 纹理质量
	Qualities.EffectsQuality = -1; // 效果质量
	Qualities.FoliageQuality = -1; // 植被质量
	Qualities.ShadingQuality = -1; // 着色质量
}

//////////////////////////////////////////////////////////////////////

// 移动设备质量包装器模板
template<typename T>
struct TMobileQualityWrapper
{
private:
	T DefaultValue; // 默认值
	TAutoConsoleVariable<FString>& WatchedVar; // 监视的控制台变量
	FString LastSeenCVarString; // 最后看到的控制台变量字符串

	struct FLimitPair // 限制对结构
	{
		int32 Limit = 0; // 限制值
		T Value = T(0); // 对应值
	};

	TArray<FLimitPair> Thresholds; // 阈值数组

public:
	TMobileQualityWrapper(T InDefaultValue, TAutoConsoleVariable<FString>& InWatchedVar)
		: DefaultValue(InDefaultValue)
		, WatchedVar(InWatchedVar)
	{
	}

	/**
	 * 查询指定测试值的质量值
	 * @param TestValue 测试值
	 * @return 对应的质量值
	 */
	T Query(int32 TestValue)
	{
		UpdateCache(); // 更新缓存

		for (const FLimitPair& Pair : Thresholds)
		{
			if (TestValue >= Pair.Limit) // 如果测试值大于等于阈值
			{
				return Pair.Value; // 返回对应的质量值
			}
		}

		return DefaultValue; // 返回默认值
	}

	// 返回第一个阈值值，如果没有阈值则返回INDEX_NONE
	int32 GetFirstThreshold()
	{
		UpdateCache(); // 更新缓存
		return (Thresholds.Num() > 0) ? Thresholds[0].Limit : INDEX_NONE; // 返回第一个阈值或INDEX_NONE
	}

	// 返回所有对中的最低值，如果没有对则返回DefaultIfNoPairs
	T GetLowestValue(T DefaultIfNoPairs)
	{
		UpdateCache(); // 更新缓存
		
		T Result = DefaultIfNoPairs; // 结果初始化为默认值
		bool bFirstValue = true; // 是否是第一个值
		for (const FLimitPair& Pair : Thresholds)
		{
			if (bFirstValue)
			{
				Result = Pair.Value; // 设置第一个值
				bFirstValue = false; // 标记已处理第一个值
			}
			else
			{
				Result = FMath::Min(Result, Pair.Value); // 取最小值
			}
		}
		
		return Result; // 返回结果
	}

private:
	/**
	 * 更新缓存
	 */
	void UpdateCache()
	{
		const FString CurrentValue = WatchedVar.GetValueOnGameThread(); // 获取当前值
		if (!CurrentValue.Equals(LastSeenCVarString, ESearchCase::CaseSensitive)) // 如果值已更改
		{
			LastSeenCVarString = CurrentValue; // 更新最后看到的字符串

			Thresholds.Reset(); // 重置阈值数组

			// 解析阈值
			int32 ScanIndex = 0; // 扫描索引
			while (ScanIndex < LastSeenCVarString.Len()) // 遍历字符串
			{
				const int32 ColonIndex = LastSeenCVarString.Find(TEXT(":"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ScanIndex); // 查找冒号
				if (ColonIndex > 0)
				{
					const int32 CommaIndex = LastSeenCVarString.Find(TEXT(","), ESearchCase::CaseSensitive, ESearchDir::FromStart, ColonIndex); // 查找逗号
					const int32 EndOfPairIndex = (CommaIndex != INDEX_NONE) ? CommaIndex : LastSeenCVarString.Len(); // 确定对结束位置

					FLimitPair Pair;
					LexFromString(Pair.Limit, *LastSeenCVarString.Mid(ScanIndex, ColonIndex - ScanIndex)); // 解析限制值
					LexFromString(Pair.Value, *LastSeenCVarString.Mid(ColonIndex + 1, EndOfPairIndex - ColonIndex - 1)); // 解析对应值
					Thresholds.Add(Pair); // 添加到阈值数组

					ScanIndex = EndOfPairIndex + 1; // 更新扫描索引
				}
				else
				{
					// 格式错误处理
					UE_LOG(LogConsoleResponse, Error, TEXT("Malformed value for '%s'='%s', expecting a ':'"),
						*IConsoleManager::Get().FindConsoleObjectName(WatchedVar.AsVariable()),
						*LastSeenCVarString);
					Thresholds.Reset(); // 重置阈值数组
					break;
				}
			}

			// 对对进行排序
			Thresholds.Sort([](const FLimitPair A, const FLimitPair B) { return A.Limit < B.Limit; }); // 按限制值升序排序
		}
	}
};

// Lyra设置助手命名空间
namespace LyraSettingsHelpers
{
	/**
	 * 检查平台是否具有指定特性
	 * @param Tag 特性标签
	 * @return 如果具有特性返回true，否则返回false
	 */
	bool HasPlatformTrait(FGameplayTag Tag)
	{
		return ICommonUIModule::GetSettings().GetPlatformTraits().HasTag(Tag); // 获取平台特性并检查是否包含指定标签
	}

	/**
	 * 返回整数可伸缩性设置中的最高级别（忽略ResolutionQuality）
	 * @param ScalabilityQuality 可伸缩性质量
	 * @return 最高级别
	 */
	int32 GetHighestLevelOfAnyScalabilityChannel(const Scalability::FQualityLevels& ScalabilityQuality)
	{
		static_assert(sizeof(Scalability::FQualityLevels) == 88, "This function may need to be updated to account for new members"); // 静态断言：如果Scalability::FQualityLevels大小改变，此函数可能需要更新

		int32 MaxScalability = ScalabilityQuality.ViewDistanceQuality; // 视距质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.AntiAliasingQuality); // 抗锯齿质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.ShadowQuality); // 阴影质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.GlobalIlluminationQuality); // 全局光照质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.ReflectionQuality); // 反射质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.PostProcessQuality); // 后期处理质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.TextureQuality); // 纹理质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.EffectsQuality); // 效果质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.FoliageQuality); // 植被质量
		MaxScalability = FMath::Max(MaxScalability, ScalabilityQuality.ShadingQuality); // 着色质量

		return (MaxScalability >= 0) ? MaxScalability : -1; // 返回最高级别或-1
	}

	/**
	 * 从设备配置文件填充可伸缩性设置
	 * @param Mode 可伸缩性快照
	 * @param Suffix 后缀
	 */
	void FillScalabilitySettingsFromDeviceProfile(FLyraScalabilitySnapshot& Mode, const FString& Suffix = FString())
	{
		static_assert(sizeof(Scalability::FQualityLevels) == 88, "This function may need to be updated to account for new members"); // 静态断言：如果Scalability::FQualityLevels大小改变，此函数可能需要更新

		// 在填充之前默认输出，以便正确标记未覆盖的可伸缩性值
		// 在测试时交换设备配置文件是技术上可能的，因此最安全的方法是清除并重新填充
		Mode = FLyraScalabilitySnapshot();

		// 从设备配置文件获取各个质量设置
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.ResolutionQuality%s"), *Suffix), Mode.Qualities.ResolutionQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.ViewDistanceQuality%s"), *Suffix), Mode.Qualities.ViewDistanceQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.AntiAliasingQuality%s"), *Suffix), Mode.Qualities.AntiAliasingQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.ShadowQuality%s"), *Suffix), Mode.Qualities.ShadowQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.GlobalIlluminationQuality%s"), *Suffix), Mode.Qualities.GlobalIlluminationQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.ReflectionQuality%s"), *Suffix), Mode.Qualities.ReflectionQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.PostProcessQuality%s"), *Suffix), Mode.Qualities.PostProcessQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.TextureQuality%s"), *Suffix), Mode.Qualities.TextureQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.EffectsQuality%s"), *Suffix), Mode.Qualities.EffectsQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.FoliageQuality%s"), *Suffix), Mode.Qualities.FoliageQuality);
		Mode.bHasOverrides |= UDeviceProfileManager::GetScalabilityCVar(FString::Printf(TEXT("sg.ShadingQuality%s"), *Suffix), Mode.Qualities.ShadingQuality);
	}

	// 创建移动设备质量包装器实例
	TMobileQualityWrapper<int32> OverallQualityLimits(-1, CVarMobileQualityLimits); // 整体质量限制
	TMobileQualityWrapper<float> ResolutionQualityLimits(100.0f, CVarMobileResolutionQualityLimits); // 分辨率质量限制
	TMobileQualityWrapper<float> ResolutionQualityRecommendations(75.0f, CVarMobileResolutionQualityRecommendation); // 分辨率质量推荐

	/**
	 * 获取适用的整体质量限制
	 * @param FrameRate 帧率
	 * @return 质量限制
	 */
	int32 GetApplicableOverallQualityLimit(int32 FrameRate)
	{
		return OverallQualityLimits.Query(FrameRate); // 查询整体质量限制
	}

	/**
	 * 获取适用的分辨率质量限制
	 * @param FrameRate 帧率
	 * @return 分辨率质量限制
	 */
	float GetApplicableResolutionQualityLimit(int32 FrameRate)
	{
		return ResolutionQualityLimits.Query(FrameRate); // 查询分辨率质量限制
	}

	/**
	 * 获取适用的分辨率质量推荐
	 * @param FrameRate 帧率
	 * @return 分辨率质量推荐
	 */
	float GetApplicableResolutionQualityRecommendation(int32 FrameRate)
	{
		return ResolutionQualityRecommendations.Query(FrameRate); // 查询分辨率质量推荐
	}

	/**
	 * 将帧率限制为与整体质量兼容
	 * @param FrameRate 帧率
	 * @param OverallQuality 整体质量
	 * @return 兼容的帧率
	 */
	int32 ConstrainFrameRateToBeCompatibleWithOverallQuality(int32 FrameRate, int32 OverallQuality)
	{
		const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
		const TArray<int32>& PossibleRates = PlatformSettings->MobileFrameRateLimits; // 获取可能的帧率

		// 选择最接近用户偏好帧率（不超过）的受支持且与所需整体质量兼容的帧率
		int32 LimitIndex = PossibleRates.FindLastByPredicate([=](const int32& TestRate)
		{
			const bool bAtOrBelowDesiredRate = (TestRate <= FrameRate); // 是否等于或低于期望帧率

			const int32 LimitQuality = GetApplicableResolutionQualityLimit(TestRate); // 获取适用的分辨率质量限制
			const bool bQualityDoesntExceedLimit = (LimitQuality < 0) || (OverallQuality <= LimitQuality); // 质量是否不超过限制
			
			const bool bIsSupported = ULyraSettingsLocal::IsSupportedMobileFramePace(TestRate); // 是否支持该帧率步调

			return bAtOrBelowDesiredRate && bQualityDoesntExceedLimit && bIsSupported; // 返回是否满足所有条件
		});

		return PossibleRates.IsValidIndex(LimitIndex) ? PossibleRates[LimitIndex] : ULyraSettingsLocal::GetDefaultMobileFrameRate(); // 返回兼容的帧率或默认移动设备帧率
	}

	// 返回第一个受当前设备配置文件限制整体质量的帧率
	int32 GetFirstFrameRateWithQualityLimit()
	{
		return OverallQualityLimits.GetFirstThreshold(); // 获取第一个阈值
	}

	// 返回存在整体帧率限制的最低质量（如果没有限制则返回-1）
	int32 GetLowestQualityWithFrameRateLimit()
	{
		return OverallQualityLimits.GetLowestValue(-1); // 获取最低质量值
	}
}

//////////////////////////////////////////////////////////////////////

/**
 * ULyraSettingsLocal构造函数
 */
ULyraSettingsLocal::ULyraSettingsLocal()
{
	if (!HasAnyFlags(RF_ClassDefaultObject) && FSlateApplication::IsInitialized()) // 如果不是类默认对象且Slate应用已初始化
	{
		// 注册应用程序激活状态更改回调
		OnApplicationActivationStateChangedHandle = FSlateApplication::Get().OnApplicationActivationStateChanged().AddUObject(this, &ThisClass::OnAppActivationStateChanged);
	}

	bEnableScalabilitySettings = ULyraPlatformSpecificRenderingSettings::Get()->bSupportsGranularVideoQualitySettings; // 设置是否启用可伸缩性设置

	SetToDefaults(); // 设置为默认值
}

/**
 * 设置为默认值
 */
void ULyraSettingsLocal::SetToDefaults()
{
	Super::SetToDefaults(); // 调用父类方法

	bUseHeadphoneMode = false; // 不使用耳机模式
	bUseHDRAudioMode = false; // 不使用HDR音频模式
	bSoundControlBusMixLoaded = false; // 声音控制总线混合未加载
	bEnableLatencyTrackingStats = ULyraSettingsLocal::DoesPlatformSupportLatencyTrackingStats(); // 根据平台支持设置延迟跟踪统计

	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	UserChosenDeviceProfileSuffix = PlatformSettings->DefaultDeviceProfileSuffix; // 用户选择的设备配置文件后缀
	DesiredUserChosenDeviceProfileSuffix = UserChosenDeviceProfileSuffix; // 期望的用户选择设备配置文件后缀

	FrameRateLimit_InMenu = 144.0f; // 菜单中的帧率限制
	FrameRateLimit_WhenBackgrounded = 30.0f; // 后台运行时的帧率限制
	FrameRateLimit_OnBattery = 60.0f; // 电池供电时的帧率限制

	MobileFrameRateLimit = GetDefaultMobileFrameRate(); // 移动设备帧率限制
	DesiredMobileFrameRateLimit = MobileFrameRateLimit; // 期望的移动设备帧率限制
}

/**
 * 加载设置
 * @param bForceReload 是否强制重新加载
 */
void ULyraSettingsLocal::LoadSettings(bool bForceReload)
{
	Super::LoadSettings(bForceReload); // 调用父类方法

	// 控制台平台使用rhi.SyncInterval来限制帧率
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::ConsoleStyle) // 如果是控制台风格帧步调
	{
		FrameRateLimit = 0.0f; // 帧率限制设为0
	}

	// 如果需要则启用HRTF
	bDesiredHeadphoneMode = bUseHeadphoneMode; // 设置期望的耳机模式
	SetHeadphoneModeEnabled(bUseHeadphoneMode); // 设置耳机模式启用状态

	ApplyLatencyTrackingStatSetting(); // 应用延迟跟踪统计设置

	DesiredUserChosenDeviceProfileSuffix = UserChosenDeviceProfileSuffix; // 设置期望的用户选择设备配置文件后缀

	LyraSettingsHelpers::FillScalabilitySettingsFromDeviceProfile(DeviceDefaultScalabilitySettings); // 从设备配置文件填充默认可伸缩性设置

	DesiredMobileFrameRateLimit = MobileFrameRateLimit; // 设置期望的移动设备帧率限制
	ClampMobileQuality(); // 钳制移动设备质量

	PerfStatSettingsChangedEvent.Broadcast(); // 广播性能统计设置更改事件
}

/**
 * 重置为当前设置
 */
void ULyraSettingsLocal::ResetToCurrentSettings()
{
	Super::ResetToCurrentSettings(); // 调用父类方法

	bDesiredHeadphoneMode = bUseHeadphoneMode; // 重置期望的耳机模式

	UserChosenDeviceProfileSuffix = DesiredUserChosenDeviceProfileSuffix; // 重置用户选择的设备配置文件后缀

	MobileFrameRateLimit = DesiredMobileFrameRateLimit; // 重置移动设备帧率限制
}

/**
 * 开始销毁对象
 */
void ULyraSettingsLocal::BeginDestroy()
{
	if (FSlateApplication::IsInitialized()) // 如果Slate应用已初始化
	{
		// 移除应用程序激活状态更改回调
		FSlateApplication::Get().OnApplicationActivationStateChanged().Remove(OnApplicationActivationStateChangedHandle);
	}

	Super::BeginDestroy(); // 调用父类方法
}

/**
 * 获取Lyra本地设置实例
 * @return Lyra本地设置实例
 */
ULyraSettingsLocal* ULyraSettingsLocal::Get()
{
	return GEngine ? CastChecked<ULyraSettingsLocal>(GEngine->GetGameUserSettings()) : nullptr; // 返回游戏用户设置
}

/**
 * 确认视频模式
 */
void ULyraSettingsLocal::ConfirmVideoMode()
{
	Super::ConfirmVideoMode(); // 调用父类方法

	SetMobileFPSMode(DesiredMobileFrameRateLimit); // 设置移动设备FPS模式
}

// 组合两个限制，始终取两个的最小值（对<=0的值有特殊处理，表示无限制）
float CombineFrameRateLimits(float Limit1, float Limit2)
{
	if (Limit1 <= 0.0f) // 如果限制1无限制
	{
		return Limit2; // 返回限制2
	}
	else if (Limit2 <= 0.0f) // 如果限制2无限制
	{
		return Limit1; // 返回限制1
	}
	else
	{
		return FMath::Min(Limit1, Limit2); // 返回两个限制的最小值
	}
}

/**
 * 获取有效帧率限制
 * @return 有效帧率限制
 */
float ULyraSettingsLocal::GetEffectiveFrameRateLimit()
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置

#if WITH_EDITOR
	if (GIsEditor && !CVarApplyFrameRateSettingsInPIE.GetValueOnGameThread()) // 如果是编辑器且不在PIE中应用帧率设置
	{
		return Super::GetEffectiveFrameRateLimit(); // 返回父类的有效帧率限制
	}
#endif

	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::ConsoleStyle) // 如果是控制台风格帧步调
	{
		return 0.0f; // 返回0
	}

	float EffectiveFrameRateLimit = Super::GetEffectiveFrameRateLimit(); // 获取父类的有效帧率限制

	if (ShouldUseFrontendPerformanceSettings()) // 如果应使用前端性能设置
	{
		EffectiveFrameRateLimit = CombineFrameRateLimits(EffectiveFrameRateLimit, FrameRateLimit_InMenu); // 组合菜单中的帧率限制
	}

	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::DesktopStyle) // 如果是桌面风格帧步调
	{
		if (FPlatformMisc::IsRunningOnBattery()) // 如果正在电池供电运行
		{
			EffectiveFrameRateLimit = CombineFrameRateLimits(EffectiveFrameRateLimit, FrameRateLimit_OnBattery); // 组合电池供电时的帧率限制
		}

		if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsActive()) // 如果Slate应用已初始化且不处于活动状态
		{
			EffectiveFrameRateLimit = CombineFrameRateLimits(EffectiveFrameRateLimit, FrameRateLimit_WhenBackgrounded); // 组合后台运行时的帧率限制
		}
	}

 	return EffectiveFrameRateLimit; // 返回有效帧率限制
}

/**
 * 获取任何可伸缩性通道的最高级别
 * @return 最高级别
 */
int32 ULyraSettingsLocal::GetHighestLevelOfAnyScalabilityChannel() const
{
	return LyraSettingsHelpers::GetHighestLevelOfAnyScalabilityChannel(ScalabilityQuality); // 返回可伸缩性质量的最高级别
}

/**
 * 根据可伸缩性模式覆盖质量级别
 * @param InMode 可伸缩性快照
 * @param InOutLevels 输入输出质量级别
 */
void ULyraSettingsLocal::OverrideQualityLevelsToScalabilityMode(const FLyraScalabilitySnapshot& InMode, Scalability::FQualityLevels& InOutLevels)
{
	static_assert(sizeof(Scalability::FQualityLevels) == 88, "This function may need to be updated to account for new members"); // 静态断言：如果Scalability::FQualityLevels大小改变，此函数可能需要更新

	// 覆盖任何有效（非负）设置
	InOutLevels.ResolutionQuality = (InMode.Qualities.ResolutionQuality >= 0.f) ? InMode.Qualities.ResolutionQuality : InOutLevels.ResolutionQuality; // 分辨率质量
	InOutLevels.ViewDistanceQuality = (InMode.Qualities.ViewDistanceQuality >= 0) ? InMode.Qualities.ViewDistanceQuality : InOutLevels.ViewDistanceQuality; // 视距质量
	InOutLevels.AntiAliasingQuality = (InMode.Qualities.AntiAliasingQuality >= 0) ? InMode.Qualities.AntiAliasingQuality : InOutLevels.AntiAliasingQuality; // 抗锯齿质量
	InOutLevels.ShadowQuality = (InMode.Qualities.ShadowQuality >= 0) ? InMode.Qualities.ShadowQuality : InOutLevels.ShadowQuality; // 阴影质量
	InOutLevels.GlobalIlluminationQuality = (InMode.Qualities.GlobalIlluminationQuality >= 0) ? InMode.Qualities.GlobalIlluminationQuality : InOutLevels.GlobalIlluminationQuality; // 全局光照质量
	InOutLevels.ReflectionQuality = (InMode.Qualities.ReflectionQuality >= 0) ? InMode.Qualities.ReflectionQuality : InOutLevels.ReflectionQuality; // 反射质量
	InOutLevels.PostProcessQuality = (InMode.Qualities.PostProcessQuality >= 0) ? InMode.Qualities.PostProcessQuality : InOutLevels.PostProcessQuality; // 后期处理质量
	InOutLevels.TextureQuality = (InMode.Qualities.TextureQuality >= 0) ? InMode.Qualities.TextureQuality : InOutLevels.TextureQuality; // 纹理质量
	InOutLevels.EffectsQuality = (InMode.Qualities.EffectsQuality >= 0) ? InMode.Qualities.EffectsQuality : InOutLevels.EffectsQuality; // 效果质量
	InOutLevels.FoliageQuality = (InMode.Qualities.FoliageQuality >= 0) ? InMode.Qualities.FoliageQuality : InOutLevels.FoliageQuality; // 植被质量
	InOutLevels.ShadingQuality = (InMode.Qualities.ShadingQuality >= 0) ? InMode.Qualities.ShadingQuality : InOutLevels.ShadingQuality; // 着色质量
}

/**
 * 根据设备配置文件钳制质量级别
 * @param ClampLevels 钳制级别
 * @param InOutLevels 输入输出质量级别
 */
void ULyraSettingsLocal::ClampQualityLevelsToDeviceProfile(const Scalability::FQualityLevels& ClampLevels, Scalability::FQualityLevels& InOutLevels)
{
	static_assert(sizeof(Scalability::FQualityLevels) == 88, "This function may need to be updated to account for new members"); // 静态断言：如果Scalability::FQualityLevels大小改变，此函数可能需要更新

	// 钳制任何有效（非负）设置
	InOutLevels.ResolutionQuality = (ClampLevels.ResolutionQuality >= 0.f) ? FMath::Min(ClampLevels.ResolutionQuality, InOutLevels.ResolutionQuality) : InOutLevels.ResolutionQuality; // 分辨率质量
	InOutLevels.ViewDistanceQuality = (ClampLevels.ViewDistanceQuality >= 0) ? FMath::Min(ClampLevels.ViewDistanceQuality, InOutLevels.ViewDistanceQuality) : InOutLevels.ViewDistanceQuality; // 视距质量
	InOutLevels.AntiAliasingQuality = (ClampLevels.AntiAliasingQuality >= 0) ? FMath::Min(ClampLevels.AntiAliasingQuality, InOutLevels.AntiAliasingQuality) : InOutLevels.AntiAliasingQuality; // 抗锯齿质量
	InOutLevels.ShadowQuality = (ClampLevels.ShadowQuality >= 0) ? FMath::Min(ClampLevels.ShadowQuality, InOutLevels.ShadowQuality) : InOutLevels.ShadowQuality; // 阴影质量
	InOutLevels.GlobalIlluminationQuality = (ClampLevels.GlobalIlluminationQuality >= 0) ? FMath::Min(ClampLevels.GlobalIlluminationQuality, InOutLevels.GlobalIlluminationQuality) : InOutLevels.GlobalIlluminationQuality; // 全局光照质量
	InOutLevels.ReflectionQuality = (ClampLevels.ReflectionQuality >= 0) ? FMath::Min(ClampLevels.ReflectionQuality, InOutLevels.ReflectionQuality) : InOutLevels.ReflectionQuality; // 反射质量
	InOutLevels.PostProcessQuality = (ClampLevels.PostProcessQuality >= 0) ? FMath::Min(ClampLevels.PostProcessQuality, InOutLevels.PostProcessQuality) : InOutLevels.PostProcessQuality; // 后期处理质量
	InOutLevels.TextureQuality = (ClampLevels.TextureQuality >= 0) ? FMath::Min(ClampLevels.TextureQuality, InOutLevels.TextureQuality) : InOutLevels.TextureQuality; // 纹理质量
	InOutLevels.EffectsQuality = (ClampLevels.EffectsQuality >= 0) ? FMath::Min(ClampLevels.EffectsQuality, InOutLevels.EffectsQuality) : InOutLevels.EffectsQuality; // 效果质量
	InOutLevels.FoliageQuality = (ClampLevels.FoliageQuality >= 0) ? FMath::Min(ClampLevels.FoliageQuality, InOutLevels.FoliageQuality) : InOutLevels.FoliageQuality; // 植被质量
	InOutLevels.ShadingQuality = (ClampLevels.ShadingQuality >= 0) ? FMath::Min(ClampLevels.ShadingQuality, InOutLevels.ShadingQuality) : InOutLevels.ShadingQuality; // 着色质量
}

/**
 * 经验加载完成时调用
 */
void ULyraSettingsLocal::OnExperienceLoaded()
{
	ReapplyThingsDueToPossibleDeviceProfileChange(); // 由于可能的设备配置文件更改而重新应用事项
}

/**
 * 热修复设备配置文件应用时调用
 */
void ULyraSettingsLocal::OnHotfixDeviceProfileApplied()
{
	ReapplyThingsDueToPossibleDeviceProfileChange(); // 由于可能的设备配置文件更改而重新应用事项
}

/**
 * 由于可能的设备配置文件更改而重新应用事项
 */
void ULyraSettingsLocal::ReapplyThingsDueToPossibleDeviceProfileChange()
{
	ApplyNonResolutionSettings(); // 应用非分辨率设置
}

/**
 * 设置是否应使用前端性能设置
 * @param bInFrontEnd 是否在前端
 */
void ULyraSettingsLocal::SetShouldUseFrontendPerformanceSettings(bool bInFrontEnd)
{
	bInFrontEndForPerformancePurposes = bInFrontEnd; // 设置是否出于性能目的在前端
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 检查是否应使用前端性能设置
 * @return 如果应使用前端性能设置返回true，否则返回false
 */
bool ULyraSettingsLocal::ShouldUseFrontendPerformanceSettings() const
{
#if WITH_EDITOR
	if (GIsEditor && !CVarApplyFrontEndPerformanceOptionsInPIE.GetValueOnGameThread()) // 如果是编辑器且不在PIE中应用前端性能选项
	{
		return false; // 返回false
	}
#endif

	return bInFrontEndForPerformancePurposes; // 返回是否出于性能目的在前端
}

/**
 * 获取性能统计显示状态
 * @param Stat 性能统计
 * @return 显示模式
 */
ELyraStatDisplayMode ULyraSettingsLocal::GetPerfStatDisplayState(ELyraDisplayablePerformanceStat Stat) const
{
	if (const ELyraStatDisplayMode* pMode = DisplayStatList.Find(Stat)) // 在显示统计列表中查找
	{
		return *pMode; // 返回找到的显示模式
	}
	else
	{
		return ELyraStatDisplayMode::Hidden; // 返回隐藏模式
	}
}

/**
 * 设置性能统计显示状态
 * @param Stat 性能统计
 * @param DisplayMode 显示模式
 */
void ULyraSettingsLocal::SetPerfStatDisplayState(ELyraDisplayablePerformanceStat Stat, ELyraStatDisplayMode DisplayMode)
{
	if (DisplayMode == ELyraStatDisplayMode::Hidden) // 如果是隐藏模式
	{
		DisplayStatList.Remove(Stat); // 从显示统计列表中移除
	}
	else
	{
		DisplayStatList.FindOrAdd(Stat) = DisplayMode; // 添加或更新显示统计列表
	}
	PerfStatSettingsChangedEvent.Broadcast(); // 广播性能统计设置更改事件
}

/**
 * 检查平台是否支持延迟标记
 * @return 如果支持返回true，否则返回false
 */
bool ULyraSettingsLocal::DoesPlatformSupportLatencyMarkers()
{
	return ICommonUIModule::GetSettings().GetPlatformTraits().HasTag(PerfStatTags::TAG_Platform_Trait_SupportsLatencyMarkers); // 检查平台是否支持延迟标记
}

/**
 * 设置启用延迟闪光指示器
 * @param bNewVal 新的启用状态
 */
void ULyraSettingsLocal::SetEnableLatencyFlashIndicators(const bool bNewVal)
{
	if (bNewVal != bEnableLatencyFlashIndicators) // 如果值已更改
	{
		bEnableLatencyFlashIndicators = bNewVal; // 设置启用延迟闪光指示器
		LatencyFlashInidicatorSettingsChangedEvent.Broadcast(); // 广播延迟闪光指示器设置更改事件
	}	
}

/**
 * 设置启用延迟跟踪统计
 * @param bNewVal 新的启用状态
 */
void ULyraSettingsLocal::SetEnableLatencyTrackingStats(const bool bNewVal)
{
	if (bNewVal != bEnableLatencyTrackingStats) // 如果值已更改
	{
		bEnableLatencyTrackingStats = bNewVal; // 设置启用延迟跟踪统计

		ApplyLatencyTrackingStatSetting(); // 应用延迟跟踪统计设置

		LatencyStatIndicatorSettingsChangedEvent.Broadcast(); // 广播延迟统计指示器设置更改事件
	}
}

/**
 * 应用延迟跟踪统计设置
 */
void ULyraSettingsLocal::ApplyLatencyTrackingStatSetting()
{
	// 由于此函数将在加载设置时调用，我们检查slate应用是否已初始化
	// 如果未初始化，那么我们处于无法具有延迟统计的目标中（如无头cooker），因此我们将提前退出并不执行任何操作
	if (!FSlateApplication::IsInitialized()) // 如果Slate应用未初始化
	{
		return; // 直接返回
	}
	
	// 如果平台甚至不支持跟踪统计，请不要费心做任何事情
	if (!DoesPlatformSupportLatencyTrackingStats()) // 如果平台不支持延迟跟踪统计
	{
		return; // 直接返回
	}
	
	// 实际上根据此设置启用或禁用延迟标记模块
	TArray<ILatencyMarkerModule*> LatencyMarkerModules = IModularFeatures::Get().GetModularFeatureImplementations<ILatencyMarkerModule>(ILatencyMarkerModule::GetModularFeatureName()); // 获取延迟标记模块
	for (ILatencyMarkerModule* LatencyMarkerModule : LatencyMarkerModules) // 遍历延迟标记模块
	{
		LatencyMarkerModule->SetEnabled(bEnableLatencyTrackingStats); // 设置启用状态
	}

	UE_CLOG(!LatencyMarkerModules.IsEmpty(), // 如果延迟标记模块不为空
		LogConsoleResponse,
		Log,
		TEXT("%s %d Latency Marker Module(s)"), // 日志格式
		bEnableLatencyTrackingStats ? TEXT("Enabled") : TEXT("Disabled"), LatencyMarkerModules.Num()); // 日志内容
}

/**
 * 检查平台是否支持延迟跟踪统计
 * @return 如果支持返回true，否则返回false
 */
bool ULyraSettingsLocal::DoesPlatformSupportLatencyTrackingStats()
{
	return ICommonUIModule::GetSettings().GetPlatformTraits().HasTag(PerfStatTags::TAG_Platform_Trait_SupportsLatencyStats); // 检查平台是否支持延迟统计
}

/**
 * 获取显示伽马值
 * @return 显示伽马值
 */
float ULyraSettingsLocal::GetDisplayGamma() const
{
	return DisplayGamma; // 返回显示伽马值
}

/**
 * 设置显示伽马值
 * @param InGamma 显示伽马值
 */
void ULyraSettingsLocal::SetDisplayGamma(float InGamma)
{
	DisplayGamma = InGamma; // 设置显示伽马值
	ApplyDisplayGamma(); // 应用显示伽马设置
}

/**
 * 应用显示伽马设置
 */
void ULyraSettingsLocal::ApplyDisplayGamma()
{
	if (GEngine) // 如果游戏引擎存在
	{
		GEngine->DisplayGamma = DisplayGamma; // 设置引擎显示伽马值
	}
}

/**
 * 获取电池供电时的帧率限制
 * @return 帧率限制
 */
float ULyraSettingsLocal::GetFrameRateLimit_OnBattery() const
{
	return FrameRateLimit_OnBattery; // 返回电池供电时的帧率限制
}

/**
 * 设置电池供电时的帧率限制
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetFrameRateLimit_OnBattery(float NewLimitFPS)
{
	FrameRateLimit_OnBattery = NewLimitFPS; // 设置电池供电时的帧率限制
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 获取菜单中的帧率限制
 * @return 帧率限制
 */
float ULyraSettingsLocal::GetFrameRateLimit_InMenu() const
{
	return FrameRateLimit_InMenu; // 返回菜单中的帧率限制
}

/**
 * 设置菜单中的帧率限制
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetFrameRateLimit_InMenu(float NewLimitFPS)
{
	FrameRateLimit_InMenu = NewLimitFPS; // 设置菜单中的帧率限制
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 获取后台运行时的帧率限制
 * @return 帧率限制
 */
float ULyraSettingsLocal::GetFrameRateLimit_WhenBackgrounded() const
{
	return FrameRateLimit_WhenBackgrounded; // 返回后台运行时的帧率限制
}

/**
 * 设置后台运行时的帧率限制
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetFrameRateLimit_WhenBackgrounded(float NewLimitFPS)
{
	FrameRateLimit_WhenBackgrounded = NewLimitFPS; // 设置后台运行时的帧率限制
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 获取始终应用的帧率限制
 * @return 帧率限制
 */
float ULyraSettingsLocal::GetFrameRateLimit_Always() const
{
	return GetFrameRateLimit(); // 返回帧率限制
}

/**
 * 设置始终应用的帧率限制
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetFrameRateLimit_Always(float NewLimitFPS)
{
	SetFrameRateLimit(NewLimitFPS); // 设置帧率限制
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 更新有效帧率限制
 */
void ULyraSettingsLocal::UpdateEffectiveFrameRateLimit()
{
	if (!IsRunningDedicatedServer()) // 如果不是专用服务器
	{
		SetFrameRateLimitCVar(GetEffectiveFrameRateLimit()); // 设置帧率限制控制台变量
	}
}

/**
 * 获取默认移动设备帧率
 * @return 默认移动设备帧率
 */
int32 ULyraSettingsLocal::GetDefaultMobileFrameRate()
{
	return CVarDeviceProfileDrivenMobileDefaultFrameRate.GetValueOnGameThread(); // 从控制台变量获取默认移动设备帧率
}

/**
 * 获取最大移动设备帧率
 * @return 最大移动设备帧率
 */
int32 ULyraSettingsLocal::GetMaxMobileFrameRate()
{
	return CVarDeviceProfileDrivenMobileMaxFrameRate.GetValueOnGameThread(); // 从控制台变量获取最大移动设备帧率
}

/**
 * 检查是否支持移动设备帧率步调
 * @param TestFPS 测试帧率
 * @return 如果支持返回true，否则返回false
 */
bool ULyraSettingsLocal::IsSupportedMobileFramePace(int32 TestFPS)
{
	const bool bIsDefault = (TestFPS == GetDefaultMobileFrameRate()); // 是否为默认帧率
	const bool bDoesNotExceedLimit = (TestFPS <= GetMaxMobileFrameRate()); // 是否不超过限制

	// 在编辑器中允许所有步调，因为我们只会在模拟其他平台时执行此操作
	const bool bIsSupportedPace = FPlatformRHIFramePacer::SupportsFramePace(TestFPS) || GIsEditor; // 是否支持帧率步调

	return bIsDefault || (bDoesNotExceedLimit && bIsSupportedPace); // 返回是否支持
}

/**
 * 获取第一个受质量限制的帧率
 * @return 第一个受质量限制的帧率
 */
int32 ULyraSettingsLocal::GetFirstFrameRateWithQualityLimit() const
{
	return LyraSettingsHelpers::GetFirstFrameRateWithQualityLimit(); // 返回第一个受质量限制的帧率
}

/**
 * 获取存在帧率限制的最低质量
 * @return 最低质量，如果没有限制则返回-1
 */
int32 ULyraSettingsLocal::GetLowestQualityWithFrameRateLimit() const
{
	return LyraSettingsHelpers::GetLowestQualityWithFrameRateLimit(); // 返回存在帧率限制的最低质量
}

/**
 * 重置为移动设备默认值
 */
void ULyraSettingsLocal::ResetToMobileDeviceDefaults()
{
	// 重置帧率
	DesiredMobileFrameRateLimit = GetDefaultMobileFrameRate(); // 设置期望的移动设备帧率限制
	MobileFrameRateLimit = DesiredMobileFrameRateLimit; // 设置移动设备帧率限制
	
	// 重置可伸缩性
	Scalability::FQualityLevels DefaultLevels = Scalability::GetQualityLevels(); // 获取默认质量级别
	OverrideQualityLevelsToScalabilityMode(DeviceDefaultScalabilitySettings, DefaultLevels); // 根据设备默认可伸缩性设置覆盖质量级别
	ScalabilityQuality = DefaultLevels; // 设置可伸缩性质量

	// 应用
	UpdateGameModeDeviceProfileAndFps(); // 更新游戏模式设备配置文件和FPS
}

/**
 * 获取最大支持的总体质量级别
 * @return 最大支持的总体质量级别
 */
int32 ULyraSettingsLocal::GetMaxSupportedOverallQualityLevel() const
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if ((PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) && DeviceDefaultScalabilitySettings.bHasOverrides) // 如果是移动设备风格帧步调且有覆盖
	{
		return LyraSettingsHelpers::GetHighestLevelOfAnyScalabilityChannel(DeviceDefaultScalabilitySettings.Qualities); // 返回设备默认可伸缩性设置的最高级别
	}
	else
	{
		return 3; // 返回默认最大级别3
	}
}

/**
 * 设置移动设备FPS模式
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetMobileFPSMode(int32 NewLimitFPS)
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) // 如果是移动设备风格帧步调
	{
		if (MobileFrameRateLimit != NewLimitFPS) // 如果移动设备帧率限制已更改
		{
			MobileFrameRateLimit = NewLimitFPS; // 设置移动设备帧率限制
			UpdateGameModeDeviceProfileAndFps(); // 更新游戏模式设备配置文件和FPS
		}

		DesiredMobileFrameRateLimit = MobileFrameRateLimit; // 设置期望的移动设备帧率限制
	}
}

/**
 * 设置期望的移动设备帧率限制
 * @param NewLimitFPS 新的帧率限制
 */
void ULyraSettingsLocal::SetDesiredMobileFrameRateLimit(int32 NewLimitFPS)
{
	const int32 OldLimitFPS = DesiredMobileFrameRateLimit; // 保存旧的帧率限制

	RemapMobileResolutionQuality(OldLimitFPS, NewLimitFPS); // 重新映射移动设备分辨率质量

	DesiredMobileFrameRateLimit = NewLimitFPS; // 设置期望的移动设备帧率限制

	ClampMobileFPSQualityLevels(/*bWriteBack=*/ false); // 钳制移动设备FPS质量级别，不写回
}

/**
 * 钳制移动设备FPS质量级别
 * @param bWriteBack 是否写回
 */
void ULyraSettingsLocal::ClampMobileFPSQualityLevels(bool bWriteBack)
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) // 如果是移动设备风格帧步调
	{
		const int32 QualityLimit = LyraSettingsHelpers::GetApplicableOverallQualityLimit(DesiredMobileFrameRateLimit); // 获取适用的整体质量限制
		const int32 CurrentQualityLevel = GetHighestLevelOfAnyScalabilityChannel(); // 获取当前质量级别
		if ((QualityLimit >= 0) && (CurrentQualityLevel > QualityLimit)) // 如果存在质量限制且当前质量级别超过限制
		{
			SetOverallScalabilityLevel(QualityLimit); // 设置整体可伸缩性级别

			if (bWriteBack) // 如果需要写回
			{
				Scalability::SetQualityLevels(ScalabilityQuality); // 设置质量级别
			}
			UE_LOG(LogConsoleResponse, Log, TEXT("Mobile FPS clamped overall quality (%d -> %d)."), CurrentQualityLevel, QualityLimit); // 记录日志
		}
	}
}

/**
 * 钳制移动设备质量
 */
void ULyraSettingsLocal::ClampMobileQuality()
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) // 如果是移动设备风格帧步调
	{
		// 将结果设置钳制到设备默认值，这是已知可行的最大值
		// 这是一个钳制而不是覆盖，以保留允许的用户设置
		Scalability::FQualityLevels CurrentLevels = Scalability::GetQualityLevels(); // 获取当前质量级别

		/** 在移动设备上，禁用3D分辨率钳制，该钳制会在启动时恢复用户设置的设置 */
		bool bMobileDisableResolutionReset = true; // 移动设备禁用分辨率重置标志
		if (bMobileDisableResolutionReset) // 如果禁用分辨率重置
		{
			DeviceDefaultScalabilitySettings.Qualities.ResolutionQuality = CurrentLevels.ResolutionQuality; // 设置设备默认可伸缩性设置的分辨率质量
		}

		ClampQualityLevelsToDeviceProfile(DeviceDefaultScalabilitySettings.Qualities, /*inout*/ CurrentLevels); // 根据设备配置文件钳制质量级别
		Scalability::SetQualityLevels(CurrentLevels); // 设置质量级别

		// 在当前帧率下如果需要则钳制质量级别
		ClampMobileFPSQualityLevels(/*bWriteBack=*/ true); // 钳制移动设备FPS质量级别，写回

		const int32 MaxMobileFrameRate = GetMaxMobileFrameRate(); // 获取最大移动设备帧率
		const int32 DefaultMobileFrameRate = GetDefaultMobileFrameRate(); // 获取默认移动设备帧率
		
		ensureMsgf(DefaultMobileFrameRate <= MaxMobileFrameRate, TEXT("Default mobile frame rate (%d) is higher than the maximum mobile frame rate (%d)!"), DefaultMobileFrameRate, MaxMobileFrameRate); // 确保默认帧率不超过最大帧率

		// 选择最接近用户期望设置的支持帧率，不超过设备施加的限制
		const TArray<int32>& PossibleRates = PlatformSettings->MobileFrameRateLimits; // 获取可能的帧率
		const int32 LimitIndex = PossibleRates.FindLastByPredicate([this](const int32& TestRate) { return (TestRate <= DesiredMobileFrameRateLimit) && IsSupportedMobileFramePace(TestRate); }); // 查找符合条件的帧率索引
		const int32 ActualLimitFPS = PossibleRates.IsValidIndex(LimitIndex) ? PossibleRates[LimitIndex] : GetDefaultMobileFrameRate(); // 获取实际限制帧率

		ClampMobileResolutionQuality(ActualLimitFPS); // 钳制移动设备分辨率质量
	}
}

/**
 * 钳制移动设备分辨率质量
 * @param TargetFPS 目标帧率
 */
void ULyraSettingsLocal::ClampMobileResolutionQuality(int32 TargetFPS)
{
	// 钳制移动设备分辨率质量
	float MaxMobileResQuality = LyraSettingsHelpers::GetApplicableResolutionQualityLimit(TargetFPS); // 获取适用的分辨率质量限制
	float CurrentScaleNormalized = 0.0f; // 当前归一化比例
	float CurrentScaleValue = 0.0f; // 当前比例值
	float MinScaleValue = 0.0f; // 最小比例值
	float MaxScaleValue = 0.0f; // 最大比例值
	GetResolutionScaleInformationEx(CurrentScaleNormalized, CurrentScaleValue, MinScaleValue, MaxScaleValue); // 获取分辨率比例信息
	if (CurrentScaleValue > MaxMobileResQuality) // 如果当前比例值超过最大移动设备分辨率质量
	{
		UE_LOG(LogConsoleResponse, Log, TEXT("clamping mobile resolution quality max res: %f, %f, %f, %f, %f"), CurrentScaleNormalized, CurrentScaleValue, MinScaleValue, MaxScaleValue, MaxMobileResQuality); // 记录日志
		SetResolutionScaleValueEx(MaxMobileResQuality); // 设置分辨率比例值
	}
}

/**
 * 重新映射移动设备分辨率质量
 * @param FromFPS 源帧率
 * @param ToFPS 目标帧率
 */
void ULyraSettingsLocal::RemapMobileResolutionQuality(int32 FromFPS, int32 ToFPS)
{
	// 移动设备分辨率质量滑块是一个归一化值，在最小质量和最大质量之间进行线性插值
	// 最大质量可以根据FPS模式更改。当FPS模式更改时，此代码重新映射质量，使归一化值在新范围内保持不变
	float CurrentScaleNormalized = 0.0f; // 当前归一化比例
	float CurrentScaleValue = 0.0f; // 当前比例值
	float MinScaleValue = 0.0f; // 最小比例值
	float MaxScaleValue = 0.0f; // 最大比例值
	GetResolutionScaleInformationEx(CurrentScaleNormalized, CurrentScaleValue, MinScaleValue, MaxScaleValue); // 获取分辨率比例信息
	float FromMaxMobileResQuality = LyraSettingsHelpers::GetApplicableResolutionQualityLimit(FromFPS); // 获取源帧率的最大移动设备分辨率质量
	float ToMaxMobileResQuality = LyraSettingsHelpers::GetApplicableResolutionQualityLimit(ToFPS); // 获取目标帧率的最大移动设备分辨率质量
	float FromMobileScaledNormalizedValue = (CurrentScaleValue - MinScaleValue) / (FromMaxMobileResQuality - MinScaleValue); // 计算源移动设备缩放归一化值
	float ToResQuality = FMath::Lerp(MinScaleValue, ToMaxMobileResQuality, FromMobileScaledNormalizedValue); // 计算目标分辨率质量

	UE_LOG(LogConsoleResponse, Log, TEXT("Remap mobile resolution quality %f, %f, (%d,%d)"), CurrentScaleValue, ToResQuality, FromFPS, ToFPS); // 记录日志

	SetResolutionScaleValueEx(ToResQuality); // 设置分辨率比例值
}

/**
 * 获取期望的设备配置文件质量后缀
 * @return 设备配置文件质量后缀
 */
FString ULyraSettingsLocal::GetDesiredDeviceProfileQualitySuffix() const
{
	return DesiredUserChosenDeviceProfileSuffix; // 返回期望的用户选择设备配置文件后缀
}

/**
 * 设置期望的设备配置文件质量后缀
 * @param InDesiredSuffix 期望的后缀
 */
void ULyraSettingsLocal::SetDesiredDeviceProfileQualitySuffix(const FString& InDesiredSuffix)
{
	DesiredUserChosenDeviceProfileSuffix = InDesiredSuffix; // 设置期望的用户选择设备配置文件后缀
}

/**
 * 设置耳机模式启用状态
 * @param bEnabled 启用状态
 */
void ULyraSettingsLocal::SetHeadphoneModeEnabled(bool bEnabled)
{
	if (CanModifyHeadphoneModeEnabled()) // 如果可以修改耳机模式启用状态
	{
		static IConsoleVariable* BinauralSpatializationDisabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("au.DisableBinauralSpatialization")); // 查找双耳空间化禁用控制台变量
		if (BinauralSpatializationDisabledCVar) // 如果控制台变量存在
		{
			BinauralSpatializationDisabledCVar->Set(!bEnabled, ECVF_SetByGameSetting); // 设置控制台变量值

			// 只有设置实际更改时才保存设置
			if (bUseHeadphoneMode != bEnabled) // 如果耳机模式使用状态已更改
			{
				bUseHeadphoneMode = bEnabled; // 设置耳机模式使用状态
				SaveSettings(); // 保存设置
			}
		}
	}
}

/**
 * 获取耳机模式是否启用
 * @return 如果启用返回true，否则返回false
 */
bool ULyraSettingsLocal::IsHeadphoneModeEnabled() const
{
	return bUseHeadphoneMode; // 返回耳机模式使用状态
}

/**
 * 检查是否可以修改耳机模式启用状态
 * @return 如果可以修改返回true，否则返回false
 */
bool ULyraSettingsLocal::CanModifyHeadphoneModeEnabled() const
{
	static IConsoleVariable* BinauralSpatializationDisabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("au.DisableBinauralSpatialization")); // 查找双耳空间化禁用控制台变量
	const bool bHRTFOptionAvailable = BinauralSpatializationDisabledCVar && ((BinauralSpatializationDisabledCVar->GetFlags() & EConsoleVariableFlags::ECVF_SetByMask) <= EConsoleVariableFlags::ECVF_SetByGameSetting); // 检查HRTF选项是否可用

	const bool bBinauralSettingControlledByOS = LyraSettingsHelpers::HasPlatformTrait(TAG_Platform_Trait_BinauralSettingControlledByOS); // 检查双耳设置是否由操作系统控制

	return bHRTFOptionAvailable && !bBinauralSettingControlledByOS; // 返回是否可以修改
}

/**
 * 获取HDR音频模式是否启用
 * @return 如果启用返回true，否则返回false
 */
bool ULyraSettingsLocal::IsHDRAudioModeEnabled() const
{
	return bUseHDRAudioMode; // 返回HDR音频模式使用状态
}

/**
 * 设置HDR音频模式启用状态
 * @param bEnabled 启用状态
 */
void ULyraSettingsLocal::SetHDRAudioModeEnabled(bool bEnabled)
{
	bUseHDRAudioMode = bEnabled; // 设置HDR音频模式使用状态

	if (GEngine) // 如果游戏引擎存在
	{
		if (const UWorld* World = GEngine->GetCurrentPlayWorld()) // 如果存在当前游戏世界
		{
			if (ULyraAudioMixEffectsSubsystem* LyraAudioMixEffectsSubsystem = World->GetSubsystem<ULyraAudioMixEffectsSubsystem>()) // 如果获取到Lyra音频混合效果子系统
			{
				LyraAudioMixEffectsSubsystem->ApplyDynamicRangeEffectsChains(bEnabled); // 应用动态范围效果链
			}
		}
	}
}

/**
 * 检查是否可以运行自动基准测试
 * @return 如果可以运行返回true，否则返回false
 */
bool ULyraSettingsLocal::CanRunAutoBenchmark() const
{
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	return PlatformSettings->bSupportsAutomaticVideoQualityBenchmark; // 返回是否支持自动视频质量基准测试
}

/**
 * 检查是否应在启动时运行自动基准测试
 * @return 如果应运行返回true，否则返回false
 */
bool ULyraSettingsLocal::ShouldRunAutoBenchmarkAtStartup() const
{
	if (!CanRunAutoBenchmark()) // 如果不能运行自动基准测试
	{
		return false; // 返回false
	}

	if (LastCPUBenchmarkResult != -1) // 如果已有CPU基准测试结果
	{
		// 已运行并加载
		return false; // 返回false
	}

	return true; // 返回true
}

/**
 * 运行自动基准测试
 * @param bSaveImmediately 是否立即保存
 */
void ULyraSettingsLocal::RunAutoBenchmark(bool bSaveImmediately)
{
	RunHardwareBenchmark(); // 运行硬件基准测试
	
	// 始终应用，可选择保存
	ApplyScalabilitySettings(); // 应用可伸缩性设置
	ApplyLatencyTrackingStatSetting(); // 应用延迟跟踪统计设置

	if (bSaveImmediately) // 如果立即保存
	{
		SaveSettings(); // 保存设置
	}
}

/**
 * 应用可伸缩性设置
 */
void ULyraSettingsLocal::ApplyScalabilitySettings()
{
	Scalability::SetQualityLevels(ScalabilityQuality); // 设置质量级别
}

/**
 * 获取总体音量
 * @return 总体音量
 */
float ULyraSettingsLocal::GetOverallVolume() const
{
	return OverallVolume; // 返回总体音量
}

/**
 * 设置总体音量
 * @param InVolume 总体音量
 */
void ULyraSettingsLocal::SetOverallVolume(float InVolume)
{
	// 缓存传入的音量值
	OverallVolume = InVolume; // 设置总体音量

	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次从UI调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 定位本地缓存的总线并设置其音量
	if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Overall"))) // 查找总体控制总线
	{
		if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
		{
			SetVolumeForControlBus(ControlBusPtr, OverallVolume); // 为控制总线设置音量
		}
	}
}

/**
 * 获取音乐音量
 * @return 音乐音量
 */
float ULyraSettingsLocal::GetMusicVolume() const
{
	return MusicVolume; // 返回音乐音量
}

/**
 * 设置音乐音量
 * @param InVolume 音乐音量
 */
void ULyraSettingsLocal::SetMusicVolume(float InVolume)
{
	// 缓存传入的音量值
	MusicVolume = InVolume; // 设置音乐音量

	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次从UI调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 定位本地缓存的总线并设置其音量
	if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Music"))) // 查找音乐控制总线
	{
		if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
		{
			SetVolumeForControlBus(ControlBusPtr, MusicVolume); // 为控制总线设置音量
		}
	}
}

/**
 * 获取音效音量
 * @return 音效音量
 */
float ULyraSettingsLocal::GetSoundFXVolume() const
{
	return SoundFXVolume; // 返回音效音量
}

/**
 * 设置音效音量
 * @param InVolume 音效音量
 */
void ULyraSettingsLocal::SetSoundFXVolume(float InVolume)
{
	// 缓存传入的音量值
	SoundFXVolume = InVolume; // 设置音效音量

	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次从UI调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 定位本地缓存的总线并设置其音量
	if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("SoundFX"))) // 查找音效控制总线
	{
		if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
		{
			SetVolumeForControlBus(ControlBusPtr, SoundFXVolume); // 为控制总线设置音量
		}
	}
}

/**
 * 获取对话音量
 * @return 对话音量
 */
float ULyraSettingsLocal::GetDialogueVolume() const
{
	return DialogueVolume; // 返回对话音量
}

/**
 * 设置对话音量
 * @param InVolume 对话音量
 */
void ULyraSettingsLocal::SetDialogueVolume(float InVolume)
{
	// 缓存传入的音量值
	DialogueVolume = InVolume; // 设置对话音量

	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次从UI调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 定位本地缓存的总线并设置其音量
	if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Dialogue"))) // 查找对话控制总线
	{
		if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
		{
			SetVolumeForControlBus(ControlBusPtr, DialogueVolume); // 为控制总线设置音量
		}
	}
}

/**
 * 获取语音聊天音量
 * @return 语音聊天音量
 */
float ULyraSettingsLocal::GetVoiceChatVolume() const
{
	return VoiceChatVolume; // 返回语音聊天音量
}

/**
 * 设置语音聊天音量
 * @param InVolume 语音聊天音量
 */
void ULyraSettingsLocal::SetVoiceChatVolume(float InVolume)
{
	// 缓存传入的音量值
	VoiceChatVolume = InVolume; // 设置语音聊天音量

	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次从UI调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 定位本地缓存的总线并设置其音量
	if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("VoiceChat"))) // 查找语音聊天控制总线
	{
		if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
		{
			SetVolumeForControlBus(ControlBusPtr, VoiceChatVolume); // 为控制总线设置音量
		}
	}
}

/**
 * 为控制总线设置音量
 * @param InSoundControlBus 声音控制总线
 * @param InVolume 音量值
 */
void ULyraSettingsLocal::SetVolumeForControlBus(USoundControlBus* InSoundControlBus, float InVolume)
{
	// 检查控制总线和控制总线混合的引用是否已加载
	// 如果此函数是第一次调用setter，则可能需要加载
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 确保在继续之前已加载
	ensureMsgf(bSoundControlBusMixLoaded, TEXT("UserControlBusMix Settings Failed to Load.")); // 确保声音控制总线混合已加载

	// 假设一切已正确加载，我们检索世界并使用AudioModulationStatics更新控制总线音量值，并将设置应用到缓存的用户控制总线混合
	if (GEngine && InSoundControlBus && bSoundControlBusMixLoaded) // 如果游戏引擎存在、声音控制总线存在且声音控制总线混合已加载
	{
			if (const UWorld* AudioWorld = GEngine->GetCurrentPlayWorld()) // 如果存在当前游戏世界
			{
				ensureMsgf(ControlBusMix, TEXT("Control Bus Mix failed to load.")); // 确保控制总线混合存在

				// 创建并设置控制总线混合阶段参数
				FSoundControlBusMixStage UpdatedControlBusMixStage;
				UpdatedControlBusMixStage.Bus = InSoundControlBus; // 设置总线
				UpdatedControlBusMixStage.Value.TargetValue = InVolume; // 设置目标值
				UpdatedControlBusMixStage.Value.AttackTime = 0.01f; // 设置启动时间
				UpdatedControlBusMixStage.Value.ReleaseTime = 0.01f; // 设置释放时间

				// 将控制总线混合阶段添加到数组，因为UpdateMix函数需要数组
				TArray<FSoundControlBusMixStage> UpdatedMixStageArray;
				UpdatedMixStageArray.Add(UpdatedControlBusMixStage); // 添加到数组

				// 修改用户控制总线混合上匹配的总线混合阶段参数
				UAudioModulationStatics::UpdateMix(AudioWorld, ControlBusMix, UpdatedMixStageArray); // 更新混合
			}
	}
}

/**
 * 设置音频输出设备ID
 * @param InAudioOutputDeviceId 音频输出设备ID
 */
void ULyraSettingsLocal::SetAudioOutputDeviceId(const FString& InAudioOutputDeviceId)
{
	AudioOutputDeviceId = InAudioOutputDeviceId; // 设置音频输出设备ID
	OnAudioOutputDeviceChanged.Broadcast(InAudioOutputDeviceId); // 广播音频输出设备更改事件
}

/**
 * 应用安全区域比例
 */
void ULyraSettingsLocal::ApplySafeZoneScale()
{
	SSafeZone::SetGlobalSafeZoneScale(GetSafeZone()); // 设置全局安全区域比例
}

/**
 * 应用非分辨率设置
 */
void ULyraSettingsLocal::ApplyNonResolutionSettings()
{
	Super::ApplyNonResolutionSettings(); // 调用父类方法

	// 检查控制总线混合引用是否已加载，
	// 可能在应用非分辨率设置时未通过UI触摸任何setter而为false
	if (!bSoundControlBusMixLoaded) // 如果声音控制总线混合未加载
	{
		LoadUserControlBusMix(); // 加载用户控制总线混合
	}

	// 在此部分中，将每个控制总线更新为当前缓存的UI设置
	{
		if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Overall"))) // 查找总体控制总线
		{
			if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
			{
				SetVolumeForControlBus(ControlBusPtr, OverallVolume); // 为控制总线设置音量
			}
		}

		if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Music"))) // 查找音乐控制总线
		{
			if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
			{
				SetVolumeForControlBus(ControlBusPtr, MusicVolume); // 为控制总线设置音量
			}
		}

		if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("SoundFX"))) // 查找音效控制总线
		{
			if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
			{
				SetVolumeForControlBus(ControlBusPtr, SoundFXVolume); // 为控制总线设置音量
			}
		}

		if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("Dialogue"))) // 查找对话控制总线
		{
			if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
			{
				SetVolumeForControlBus(ControlBusPtr, DialogueVolume); // 为控制总线设置音量
			}
		}

		if (TObjectPtr<USoundControlBus>* ControlBusDblPtr = ControlBusMap.Find(TEXT("VoiceChat"))) // 查找语音聊天控制总线
		{
			if (USoundControlBus* ControlBusPtr = *ControlBusDblPtr) // 如果控制总线存在
			{
				SetVolumeForControlBus(ControlBusPtr, VoiceChatVolume); // 为控制总线设置音量
			}
		}
	}

	if (UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(GetTypedOuter<ULocalPlayer>())) // 如果获取到通用输入子系统
	{
		InputSubsystem->SetGamepadInputType(ControllerPlatform); // 设置游戏手柄输入类型
	}

	if (bUseHeadphoneMode != bDesiredHeadphoneMode) // 如果耳机模式使用状态与期望状态不一致
	{
		SetHeadphoneModeEnabled(bDesiredHeadphoneMode); // 设置耳机模式启用状态
	}
	
	if (DesiredUserChosenDeviceProfileSuffix != UserChosenDeviceProfileSuffix) // 如果期望的用户选择设备配置文件后缀与实际不一致
	{
		UserChosenDeviceProfileSuffix = DesiredUserChosenDeviceProfileSuffix; // 设置用户选择的设备配置文件后缀
	}

	if (FApp::CanEverRender()) // 如果可以渲染
	{
		ApplyDisplayGamma(); // 应用显示伽马设置
		ApplySafeZoneScale(); // 应用安全区域比例
		UpdateGameModeDeviceProfileAndFps(); // 更新游戏模式设备配置文件和FPS
	}

	PerfStatSettingsChangedEvent.Broadcast(); // 广播性能统计设置更改事件
}

/**
 * 获取整体可伸缩性级别
 * @return 整体可伸缩性级别
 */
int32 ULyraSettingsLocal::GetOverallScalabilityLevel() const
{
	int32 Result = Super::GetOverallScalabilityLevel(); // 获取父类的整体可伸缩性级别

	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) // 如果是移动设备风格帧步调
	{
		Result = GetHighestLevelOfAnyScalabilityChannel(); // 获取任何可伸缩性通道的最高级别
	}

	return Result; // 返回结果
}

/**
 * 设置整体可伸缩性级别
 * @param Value 可伸缩性级别值
 */
void ULyraSettingsLocal::SetOverallScalabilityLevel(int32 Value)
{
	TGuardValue Guard(bSettingOverallQualityGuard, true); // 设置整体质量保护

	Value = FMath::Clamp(Value, 0, 3); // 钳制值在0-3范围内

	float CurrentMobileResolutionQuality = ScalabilityQuality.ResolutionQuality; // 保存当前移动设备分辨率质量

	Super::SetOverallScalabilityLevel(Value); // 调用父类方法

	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	if (PlatformSettings->FramePacingMode == ELyraFramePacingMode::MobileStyle) // 如果是移动设备风格帧步调
	{
		// 恢复分辨率质量，移动设备将其与整体质量解耦
		ScalabilityQuality.ResolutionQuality = CurrentMobileResolutionQuality; // 恢复分辨率质量

		// 更改整体质量最终可能会调整移动设备上的帧率，因为存在限制
		const int32 ConstrainedFrameRateLimit = LyraSettingsHelpers::ConstrainFrameRateToBeCompatibleWithOverallQuality(DesiredMobileFrameRateLimit, Value); // 将帧率限制为与整体质量兼容
		if (ConstrainedFrameRateLimit != DesiredMobileFrameRateLimit) // 如果约束后的帧率限制与期望的移动设备帧率限制不同
		{
			SetDesiredMobileFrameRateLimit(ConstrainedFrameRateLimit); // 设置期望的移动设备帧率限制
		}
	}
}

/**
 * 设置控制器平台
 * @param InControllerPlatform 控制器平台
 */
void ULyraSettingsLocal::SetControllerPlatform(const FName InControllerPlatform)
{
	if (ControllerPlatform != InControllerPlatform) // 如果控制器平台已更改
	{
		ControllerPlatform = InControllerPlatform; // 设置控制器平台

		// 将更改应用到通用输入子系统，以便刷新我们正在使用的任何输入图标
		if (UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(GetTypedOuter<ULocalPlayer>())) // 如果获取到通用输入子系统
		{
			InputSubsystem->SetGamepadInputType(ControllerPlatform); // 设置游戏手柄输入类型
		}
	}
}

/**
 * 获取控制器平台
 * @return 控制器平台
 */
FName ULyraSettingsLocal::GetControllerPlatform() const
{
	return ControllerPlatform; // 返回控制器平台
}

/**
 * 加载用户控制总线混合
 */
void ULyraSettingsLocal::LoadUserControlBusMix()
{
	if (GEngine) // 如果游戏引擎存在
	{
		if (const UWorld* World = GEngine->GetCurrentPlayWorld()) // 如果存在当前游戏世界
		{
			if (const ULyraAudioSettings* LyraAudioSettings = GetDefault<ULyraAudioSettings>()) // 如果获取到Lyra音频设置
			{
				USoundControlBus* OverallControlBus = nullptr; // 总体控制总线
				USoundControlBus* MusicControlBus = nullptr; // 音乐控制总线
				USoundControlBus* SoundFXControlBus = nullptr; // 音效控制总线
				USoundControlBus* DialogueControlBus = nullptr; // 对话控制总线
				USoundControlBus* VoiceChatControlBus = nullptr; // 语音聊天控制总线

				ControlBusMap.Empty(); // 清空控制总线映射

				// 加载总体音量控制总线
				if (UObject* ObjPath = LyraAudioSettings->OverallVolumeControlBus.TryLoad()) // 尝试加载总体音量控制总线
				{
					if (USoundControlBus* SoundControlBus = Cast<USoundControlBus>(ObjPath)) // 如果转换为声音控制总线成功
					{
						OverallControlBus = SoundControlBus; // 设置总体控制总线
						ControlBusMap.Add(TEXT("Overall"), OverallControlBus); // 添加到控制总线映射
					}
					else
					{
						ensureMsgf(SoundControlBus, TEXT("Overall Control Bus reference missing from Lyra Audio Settings.")); // 确保声音控制总线存在
					}
				}

				// 加载音乐音量控制总线
				if (UObject* ObjPath = LyraAudioSettings->MusicVolumeControlBus.TryLoad()) // 尝试加载音乐音量控制总线
				{
					if (USoundControlBus* SoundControlBus = Cast<USoundControlBus>(ObjPath)) // 如果转换为声音控制总线成功
					{
						MusicControlBus = SoundControlBus; // 设置音乐控制总线
						ControlBusMap.Add(TEXT("Music"), MusicControlBus); // 添加到控制总线映射
					}
					else
					{
						ensureMsgf(SoundControlBus, TEXT("Music Control Bus reference missing from Lyra Audio Settings.")); // 确保声音控制总线存在
					}
				}

				// 加载音效音量控制总线
				if (UObject* ObjPath = LyraAudioSettings->SoundFXVolumeControlBus.TryLoad()) // 尝试加载音效音量控制总线
				{
					if (USoundControlBus* SoundControlBus = Cast<USoundControlBus>(ObjPath)) // 如果转换为声音控制总线成功
					{
						SoundFXControlBus = SoundControlBus; // 设置音效控制总线
						ControlBusMap.Add(TEXT("SoundFX"), SoundFXControlBus); // 添加到控制总线映射
					}
					else
					{
						ensureMsgf(SoundControlBus, TEXT("SoundFX Control Bus reference missing from Lyra Audio Settings.")); // 确保声音控制总线存在
					}
				}

				// 加载对话音量控制总线
				if (UObject* ObjPath = LyraAudioSettings->DialogueVolumeControlBus.TryLoad()) // 尝试加载对话音量控制总线
				{
					if (USoundControlBus* SoundControlBus = Cast<USoundControlBus>(ObjPath)) // 如果转换为声音控制总线成功
					{
						DialogueControlBus = SoundControlBus; // 设置对话控制总线
						ControlBusMap.Add(TEXT("Dialogue"), DialogueControlBus); // 添加到控制总线映射
					}
					else
					{
						ensureMsgf(SoundControlBus, TEXT("Dialogue Control Bus reference missing from Lyra Audio Settings.")); // 确保声音控制总线存在
					}
				}

				// 加载语音聊天音量控制总线
				if (UObject* ObjPath = LyraAudioSettings->VoiceChatVolumeControlBus.TryLoad()) // 尝试加载语音聊天音量控制总线
				{
					if (USoundControlBus* SoundControlBus = Cast<USoundControlBus>(ObjPath)) // 如果转换为声音控制总线成功
					{
						VoiceChatControlBus = SoundControlBus; // 设置语音聊天控制总线
						ControlBusMap.Add(TEXT("VoiceChat"), VoiceChatControlBus); // 添加到控制总线映射
					}
					else
					{
						ensureMsgf(SoundControlBus, TEXT("VoiceChat Control Bus reference missing from Lyra Audio Settings.")); // 确保声音控制总线存在
					}
				}

				// 加载用户设置控制总线混合
				if (UObject* ObjPath = LyraAudioSettings->UserSettingsControlBusMix.TryLoad()) // 尝试加载用户设置控制总线混合
				{
					if (USoundControlBusMix* SoundControlBusMix = Cast<USoundControlBusMix>(ObjPath)) // 如果转换为声音控制总线混合成功
					{
						ControlBusMix = SoundControlBusMix; // 设置控制总线混合

						// 创建各个控制总线混合阶段
						const FSoundControlBusMixStage OverallControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, OverallControlBus, OverallVolume); // 总体控制总线混合阶段
						const FSoundControlBusMixStage MusicControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, MusicControlBus, MusicVolume); // 音乐控制总线混合阶段
						const FSoundControlBusMixStage SoundFXControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, SoundFXControlBus, SoundFXVolume); // 音效控制总线混合阶段
						const FSoundControlBusMixStage DialogueControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, DialogueControlBus, DialogueVolume); // 对话控制总线混合阶段
						const FSoundControlBusMixStage VoiceChatControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, VoiceChatControlBus, VoiceChatVolume); // 语音聊天控制总线混合阶段

						// 创建控制总线混合阶段数组
						TArray<FSoundControlBusMixStage> ControlBusMixStageArray;
						ControlBusMixStageArray.Add(OverallControlBusMixStage); // 添加总体控制总线混合阶段
						ControlBusMixStageArray.Add(MusicControlBusMixStage); // 添加音乐控制总线混合阶段
						ControlBusMixStageArray.Add(SoundFXControlBusMixStage); // 添加音效控制总线混合阶段
						ControlBusMixStageArray.Add(DialogueControlBusMixStage); // 添加对话控制总线混合阶段
						ControlBusMixStageArray.Add(VoiceChatControlBusMixStage); // 添加语音聊天控制总线混合阶段

						// 更新混合
						UAudioModulationStatics::UpdateMix(World, ControlBusMix, ControlBusMixStageArray); // 更新控制总线混合

						bSoundControlBusMixLoaded = true; // 标记声音控制总线混合已加载
					}
					else
					{
						ensureMsgf(SoundControlBusMix, TEXT("User Settings Control Bus Mix reference missing from Lyra Audio Settings.")); // 确保声音控制总线混合存在
					}
				}
			}
		}
	}
}

/**
 * 应用程序激活状态更改时调用
 * @param bIsActive 是否激活
 */
void ULyraSettingsLocal::OnAppActivationStateChanged(bool bIsActive)
{
	// 我们可能希望在应用程序在多窗口平台上失去/获得焦点时调整帧率
	UpdateEffectiveFrameRateLimit(); // 更新有效帧率限制
}

/**
 * 更新游戏模式设备配置文件和FPS
 */
void ULyraSettingsLocal::UpdateGameModeDeviceProfileAndFps()
{
#if WITH_EDITOR
	if (GIsEditor && !CVarApplyDeviceProfilesInPIE.GetValueOnGameThread()) // 如果是编辑器且不在PIE中应用设备配置文件
	{
		return; // 直接返回
	}
#endif

	UDeviceProfileManager& Manager = UDeviceProfileManager::Get(); // 获取设备配置文件管理器

	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	const TArray<FLyraQualityDeviceProfileVariant>& UserFacingVariants = PlatformSettings->UserFacingDeviceProfileOptions; // 获取面向用户的设备配置文件变体

	//@TODO: 可能希望允许特定经验指定要尝试使用的后缀
	// 下面的代码将处理使用此后缀（单独或与帧率结合）进行搜索，但目前没有设置它
	FString ExperienceSuffix; // 经验后缀

	// 确保所选设置受当前显示支持，向下遍历列表以尝试回退
	const int32 PlatformMaxRefreshRate = FPlatformMisc::GetMaxRefreshRate(); // 获取平台最大刷新率

	// 查找用户选择的设备配置文件后缀的索引
	int32 SuffixIndex = UserFacingVariants.IndexOfByPredicate([&](const FLyraQualityDeviceProfileVariant& Data){ return Data.DeviceProfileSuffix == UserChosenDeviceProfileSuffix; });
	while (UserFacingVariants.IsValidIndex(SuffixIndex)) // 当索引有效时
	{
		if (PlatformMaxRefreshRate >= UserFacingVariants[SuffixIndex].MinRefreshRate) // 如果平台最大刷新率大于等于最小刷新率
		{
			break; // 跳出循环
		}
		else
		{
			--SuffixIndex; // 递减索引
		}
	}

	// 获取有效的用户后缀
	const FString EffectiveUserSuffix = UserFacingVariants.IsValidIndex(SuffixIndex) ? UserFacingVariants[SuffixIndex].DeviceProfileSuffix : PlatformSettings->DefaultDeviceProfileSuffix;

	// 构建要尝试的名称列表
	const bool bHadUserSuffix = !EffectiveUserSuffix.IsEmpty(); // 是否有用户后缀
	const bool bHadExperienceSuffix = !ExperienceSuffix.IsEmpty(); // 是否有经验后缀

	FString BasePlatformName = UDeviceProfileManager::GetPlatformDeviceProfileName(); // 获取基础平台名称
	FName PlatformName; // 平台名称，除非在编辑器中否则为默认
#if WITH_EDITOR
	if (GIsEditor) // 如果在编辑器中
	{
		const ULyraPlatformEmulationSettings* Settings = GetDefault<ULyraPlatformEmulationSettings>(); // 获取Lyra平台模拟设置
		const FName PretendBaseDeviceProfile = Settings->GetPretendBaseDeviceProfile(); // 获取假装基础设备配置文件
		if (PretendBaseDeviceProfile != NAME_None) // 如果假装基础设备配置文件不为空
		{
			BasePlatformName = PretendBaseDeviceProfile.ToString(); // 设置基础平台名称
		}

		PlatformName = Settings->GetPretendPlatformName(); // 获取假装平台名称
	}
#endif

	// 构建组合名称列表
	TArray<FString> ComposedNamesToFind;
	if (bHadExperienceSuffix && bHadUserSuffix) // 如果有经验后缀和用户后缀
	{
		ComposedNamesToFind.Add(BasePlatformName + TEXT("_") + ExperienceSuffix + TEXT("_") + EffectiveUserSuffix); // 添加组合名称
	}
	if (bHadUserSuffix) // 如果有用户后缀
	{
		ComposedNamesToFind.Add(BasePlatformName + TEXT("_") + EffectiveUserSuffix); // 添加组合名称
	}
	if (bHadExperienceSuffix) // 如果有经验后缀
	{
		ComposedNamesToFind.Add(BasePlatformName + TEXT("_") + ExperienceSuffix); // 添加组合名称
	}
	if (GIsEditor) // 如果在编辑器中
	{
		ComposedNamesToFind.Add(BasePlatformName); // 添加基础平台名称
	}

	// 查看任何潜在设备配置文件是否实际存在
	FString ActualProfileToApply; // 要应用的实际配置文件
	for (const FString& TestProfileName : ComposedNamesToFind) // 遍历组合名称
	{
		if (Manager.HasLoadableProfileName(TestProfileName, PlatformName)) // 如果具有可加载的配置文件名称
		{
			ActualProfileToApply = TestProfileName; // 设置要应用的实际配置文件
			UDeviceProfile* Profile = Manager.FindProfile(TestProfileName, /*bCreateOnFail=*/ false); // 查找配置文件
			if (Profile == nullptr) // 如果配置文件不存在
			{
				Profile = Manager.CreateProfile(TestProfileName, TEXT(""), TestProfileName, *PlatformName.ToString()); // 创建配置文件
			}

			UE_LOG(LogConsoleResponse, Log, TEXT("Profile %s exists"), *Profile->GetName()); // 记录日志
			break; // 跳出循环
		}
	}

	UE_LOG(LogConsoleResponse, Log, TEXT("UpdateGameModeDeviceProfileAndFps MaxRefreshRate=%d, ExperienceSuffix='%s', UserPicked='%s'->'%s', PlatformBase='%s', AppliedActual='%s'"), 
		PlatformMaxRefreshRate, *ExperienceSuffix, *UserChosenDeviceProfileSuffix, *EffectiveUserSuffix, *BasePlatformName, *ActualProfileToApply); // 记录日志

	// 如果与当前拥有的不同，则应用设备配置文件
	if (ActualProfileToApply != CurrentAppliedDeviceProfileOverrideSuffix) // 如果要应用的实际配置文件与当前应用的不同
	{
		if (Manager.GetActiveDeviceProfileName() != ActualProfileToApply) // 如果活动设备配置文件名称与实际要应用的不同
		{
			// 首先恢复默认值
			if (GIsEditor) // 如果在编辑器中
			{
#if ALLOW_OTHER_PLATFORM_CONFIG
				Manager.RestorePreviewDeviceProfile(); // 恢复预览设备配置文件
#endif
			}
			else
			{
				Manager.RestoreDefaultDeviceProfile(); // 恢复默认设备配置文件
			}

			// 应用新的配置文件（如果不是默认值）
			if (Manager.GetActiveDeviceProfileName() != ActualProfileToApply) // 如果活动设备配置文件名称与实际要应用的不同
			{
				UDeviceProfile* NewDeviceProfile = Manager.FindProfile(ActualProfileToApply); // 查找新设备配置文件
				ensureMsgf(NewDeviceProfile != nullptr, TEXT("DeviceProfile %s not found "), *ActualProfileToApply); // 确保新设备配置文件存在
				if (NewDeviceProfile) // 如果新设备配置文件存在
				{
					if (GIsEditor) // 如果在编辑器中
					{
#if ALLOW_OTHER_PLATFORM_CONFIG
						UE_LOG(LogConsoleResponse, Log, TEXT("Overriding *preview* device profile to %s"), *ActualProfileToApply); // 记录日志
						Manager.SetPreviewDeviceProfile(NewDeviceProfile); // 设置预览设备配置文件

						// 从假装配置文件重新加载默认设置
						LyraSettingsHelpers::FillScalabilitySettingsFromDeviceProfile(DeviceDefaultScalabilitySettings); // 从设备配置文件填充默认可伸缩性设置
#endif
					}
					else
					{
						UE_LOG(LogConsoleResponse, Log, TEXT("Overriding device profile to %s"), *ActualProfileToApply); // 记录日志
						Manager.SetOverrideDeviceProfile(NewDeviceProfile); // 设置覆盖设备配置文件

						if (!bEnableScalabilitySettings) // 如果不启用可伸缩性设置
						{
							// 我们不支持可伸缩性设置的持久性，但至少可以在任何人使用设置API查询它们时提供最新值
							ScalabilityQuality = Scalability::GetQualityLevels(); // 设置可伸缩性质量
						}
					}
				}
			}
		}
		CurrentAppliedDeviceProfileOverrideSuffix = ActualProfileToApply; // 设置当前应用的设备配置文件覆盖后缀
	}

	// 根据平台设置更新帧步调
	switch (PlatformSettings->FramePacingMode) // 根据帧步调模式
	{
	case ELyraFramePacingMode::MobileStyle: // 移动设备风格
		UpdateMobileFramePacing(); // 更新移动设备帧步调
		break;
	case ELyraFramePacingMode::ConsoleStyle: // 控制台风格
		UpdateConsoleFramePacing(); // 更新控制台帧步调
		break;
	case ELyraFramePacingMode::DesktopStyle: // 桌面风格
		UpdateDesktopFramePacing(); // 更新桌面帧步调
		break;
	}
}

/**
 * 更新控制台帧步调
 */
void ULyraSettingsLocal::UpdateConsoleFramePacing()
{
	// 应用设备配置文件驱动的帧同步和帧步调
	const int32 FrameSyncType = CVarDeviceProfileDrivenFrameSyncType.GetValueOnGameThread(); // 获取帧同步类型
	if (FrameSyncType != -1) // 如果帧同步类型有效
	{
		UE_LOG(LogConsoleResponse, Log, TEXT("Setting frame sync mode to %d."), FrameSyncType); // 记录日志
		SetSyncTypeCVar(FrameSyncType); // 设置同步类型控制台变量
	}

	const int32 TargetFPS = CVarDeviceProfileDrivenTargetFps.GetValueOnGameThread(); // 获取目标FPS
	if (TargetFPS != -1) // 如果目标FPS有效
	{
		UE_LOG(LogConsoleResponse, Log, TEXT("Setting frame pace to %d Hz."), TargetFPS); // 记录日志
		FPlatformRHIFramePacer::SetFramePace(TargetFPS); // 设置帧步调

		// 设置CSV元数据和分析FPS模式字符串
#if CSV_PROFILER
		const FString TargetFramerateString = FString::Printf(TEXT("%d"), TargetFPS); // 目标帧率字符串
		CSV_METADATA(TEXT("TargetFramerate"), *TargetFramerateString); // 设置CSV元数据
#endif
	}
}

/**
 * 更新桌面帧步调
 */
void ULyraSettingsLocal::UpdateDesktopFramePacing()
{
	// 对于桌面，帧率限制由父类基于已通过UpdateEffectiveFrameRateLimit()应用的值处理
	// 因此此函数仅处理桌面帧步调偏好的"二阶"效应

	const float TargetFPS = GetEffectiveFrameRateLimit(); // 获取目标FPS
	const float ClampedFPS = (TargetFPS <= 0.0f) ? 60.0f : FMath::Clamp(TargetFPS, 30.0f, 60.0f); // 钳制FPS
	UpdateDynamicResFrameTime(ClampedFPS); // 更新动态分辨率帧时间
}

/**
 * 更新移动设备帧步调
 */
void ULyraSettingsLocal::UpdateMobileFramePacing()
{
	//@TODO: 处理移动设备上前端或低电量模式的不同限制

	// 选择最接近用户期望设置的支持帧率，不超过设备施加的限制
	const ULyraPlatformSpecificRenderingSettings* PlatformSettings = ULyraPlatformSpecificRenderingSettings::Get(); // 获取平台特定渲染设置
	const TArray<int32>& PossibleRates = PlatformSettings->MobileFrameRateLimits; // 获取可能的帧率
	const int32 LimitIndex = PossibleRates.FindLastByPredicate([this](const int32& TestRate) { return (TestRate <= MobileFrameRateLimit) && IsSupportedMobileFramePace(TestRate); }); // 查找符合条件的帧率索引
	const int32 TargetFPS = PossibleRates.IsValidIndex(LimitIndex) ? PossibleRates[LimitIndex] : GetDefaultMobileFrameRate(); // 获取目标FPS

	UE_LOG(LogConsoleResponse, Log, TEXT("Setting frame pace to %d Hz."), TargetFPS); // 记录日志
	FPlatformRHIFramePacer::SetFramePace(TargetFPS); // 设置帧步调

	ClampMobileQuality(); // 钳制移动设备质量

	UpdateDynamicResFrameTime((float)TargetFPS); // 更新动态分辨率帧时间
}

/**
 * 更新动态分辨率帧时间
 * @param TargetFPS 目标FPS
 */
void ULyraSettingsLocal::UpdateDynamicResFrameTime(float TargetFPS)
{
	static IConsoleVariable* CVarDyResFrameTimeBudget = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DynamicRes.FrameTimeBudget")); // 查找动态分辨率帧时间预算控制台变量
	if (CVarDyResFrameTimeBudget) // 如果控制台变量存在
	{
		if (ensure(TargetFPS > 0.0f)) // 确保目标FPS大于0
		{
			const float DyResFrameTimeBudget = 1000.0f / TargetFPS; // 计算动态分辨率帧时间预算
			CVarDyResFrameTimeBudget->Set(DyResFrameTimeBudget, ECVF_SetByGameSetting); // 设置控制台变量值
		}
	}
}